﻿#include "NPCScript.h"
#include "../../Settings.h"
#include "../NPCObject.h"
#include "../../MirEnvir/Map.h"
#include "../../MirDatabase/MonsterInfo.h"
#include "../../MirEnvir/Robot.h"
#include "../../MirDatabase/QuestInfo.h"
#include "../../../Shared/ServerPackets.h"
#include "../../../Shared/Language.h"
#include "../../../Shared/Data/Stat.h"

using namespace Server::MirDatabase;
using namespace Server::MirEnvir;
using namespace Server::MirObjects;
namespace S = ServerPackets;

namespace Server::MirObjects
{

    Server::MirEnvir::Envir *NPCScript::getEnvir()
    {
        return Envir::getMain();
    }

    Server::MessageQueue *NPCScript::getMessageQueue()
    {
        return MessageQueue::getInstance();
    }

    NPCScript *NPCScript::Get(int index)
    {
        return getEnvir()->Scripts[index];
    }

    NPCScript *NPCScript::GetOrAdd(unsigned int loadedObjectID, const std::string &fileName, NPCScriptType type)
    {
        auto script = getEnvir()->Scripts.SingleOrDefault([&] (std::any x)
        {
            return x->Value->FileName->Equals(fileName, StringComparison::OrdinalIgnoreCase) && x->Value->LoadedObjectID == loadedObjectID;
        })->Value;

        if (script != nullptr)
        {
            return script;
        }

        return new NPCScript(loadedObjectID, fileName, type);
    }

const std::string MainKey, BuyKey, SellKey, BuySellKey, RepairKey, SRepairKey, RefineKey, RefineCheckKey, RefineCollectKey, ReplaceWedRingKey, BuyBackKey, StorageKey, ConsignKey, MarketKey, CraftKey, GuildCreateKey, RequestWarKey, SendParcelKey, CollectParcelKey, AwakeningKey, DisassembleKey, DowngradeKey, ResetKey, PearlBuyKey, BuyUsedKey, TradeKey, RecipeKey, TypeKey, UsedTypeKey, QuestKey, NPCScript::SpeechKey = "[SPEECH]";

    NPCScript::NPCScript(unsigned int loadedObjectID, const std::string &fileName, NPCScriptType type) : ScriptID(getEnvir()->ScriptIndex), LoadedObjectID(loadedObjectID), Type(type), FileName(fileName)
    {


        Load();

        getEnvir()->Scripts.emplace(ScriptID, this);
    }

    void NPCScript::Load()
    {
        LoadInfo();
        LoadGoods();
    }

    float NPCScript::PriceRate(PlayerObject *player, bool baseRate)
    {
        auto callingNPC = getEnvir()->NPCs.SingleOrDefault([&] (std::any x)
        {
            return x->ObjectID == player->NPCObjectID;
        });

        if (callingNPC == nullptr)
        {
            return 1.0F;
        }

        if (callingNPC->Conq == nullptr || baseRate)
        {
            return callingNPC->Info.Rate / 100.0F;
        }

        if (player->MyGuild != nullptr && player->MyGuild->Guildindex == callingNPC->Conq.Owner)
        {
            return callingNPC->Info.Rate / 100.0F;
        }
        else
        {
            return (((callingNPC->Info.Rate / 100.0F) * callingNPC->Conq.npcRate) + callingNPC->Info.Rate) / 100.0F;
        }
    }

    void NPCScript::LoadInfo()
    {
        ClearInfo();

        if (!FileSystem::directoryExists(Settings::NPCPath))
        {
            return;
        }

        std::string fileName = FileSystem::combine(Settings::NPCPath, FileName + ".txt");

        if (FileSystem::fileExists(fileName))
        {
            std::vector<std::string> lines = File::ReadAllLines(fileName).ToList();

            lines = ParseInsert(lines);
            lines = ParseInclude(lines);

            switch (Type)
            {
                case NPCScriptType::Normal:
                default:
                    ParseScript(lines);
                    break;
                case NPCScriptType::AutoPlayer:
                case NPCScriptType::AutoMonster:
                case NPCScriptType::Robot:
                    ParseDefault(lines);
                    break;
            }
        }
        else
        {
            getMessageQueue()->Enqueue(StringHelper::formatSimple("Script Not Found: {0}", FileName));
        }
    }

    void NPCScript::ClearInfo()
    {
        Goods = std::vector<UserItem*>();
        Types = std::vector<ItemType>();
        UsedTypes = std::vector<ItemType>();
        NPCPages = std::vector<NPCPage*>();
        CraftGoods = std::vector<RecipeInfo*>();

        if (Type == NPCScriptType::AutoPlayer)
        {
            getEnvir()->CustomCommands.clear();
        }
    }

    void NPCScript::LoadGoods()
    {
        auto loadedNPC = NPCObject::Get(LoadedObjectID);

        if (loadedNPC != nullptr)
        {
            loadedNPC->UsedGoods.clear();

            std::string path = FileSystem::combine(Settings::GoodsPath, std::to_string(loadedNPC->Info->Index) + ".msd");

            if (!FileSystem::fileExists(path))
            {
                return;
            }

//C# TO C++ CONVERTER NOTE: The following 'using' block is replaced by its C++ equivalent:
//ORIGINAL LINE: using (FileStream stream = File.OpenRead(path))
            {
                FileStream stream = File::OpenRead(path);
//C# TO C++ CONVERTER NOTE: The following 'using' block is replaced by its C++ equivalent:
//ORIGINAL LINE: using (BinaryReader reader = new BinaryReader(stream))
                {
                    BinaryReader reader = BinaryReader(stream);
                    int version = reader.ReadInt32();
                    int count = version;
                    int customversion = Envir::LoadCustomVersion;
                    if (version == 9999) //the only real way to tell if the file was made before or after version code got added: assuming nobody had a config option to save more then 10000 sold items
                    {
                        version = reader.ReadInt32();
                        customversion = reader.ReadInt32();
                        count = reader.ReadInt32();
                    }
                    else
                    {
                        version = Envir::LoadVersion;
                    }

                    for (int k = 0; k < count; k++)
                    {
                        UserItem *item = new UserItem(reader, version, customversion);
                        if (getEnvir()->BindItem(item))
                        {
                            loadedNPC->UsedGoods.push_back(item);
                        }

//C# TO C++ CONVERTER TODO TASK: A 'delete item' statement was not added since item was passed to a method or constructor. Handle memory management manually.
                    }
                }
            }
        }
    }

    void NPCScript::ParseDefault(std::vector<std::string> &lines)
    {
        for (int i = 0; i < lines.size(); i++)
        {
            if (!StringHelper::startsWith(StringHelper::toUpper(lines[i]), "[@_"))
            {
                continue;
            }

            if (Type == NPCScriptType::AutoPlayer)
            {
                if (StringHelper::toUpper(lines[i]).find("MAPCOORD") != std::string::npos)
                {
                    Regex *regex = new Regex(R"(\((.*?),([0-9]{1,3}),([0-9]{1,3})\))");
                    Match *match = regex->Match(lines[i]);

                    if (!match->Success)
                    {
                        delete regex;
                        continue;
                    }

                    Map *map = getEnvir()->MapList.FirstOrDefault([&] (std::any m)
                    {
                        return m::Info->FileName == match->Groups[1]->Value;
                    });

                    if (map == nullptr)
                    {
                        delete regex;
                        continue;
                    }

                    Point *point = new Point(StringHelper::fromString<short>(match->Groups[2]->Value), StringHelper::fromString<short>(match->Groups[3]->Value));

                    if (!std::find(map->Info->ActiveCoords.begin(), map->Info->ActiveCoords.end(), point) != map->Info->ActiveCoords.end())
                    {
                        map->Info->ActiveCoords.push_back(point);
                    }

//C# TO C++ CONVERTER TODO TASK: A 'delete point' statement was not added since point was passed to a method or constructor. Handle memory management manually.
                    delete regex;
                }

                if (StringHelper::toUpper(lines[i]).find("CUSTOMCOMMAND") != std::string::npos)
                {
                    Regex *regex = new Regex(R"(\((.*?)\))");
                    Match *match = regex->Match(lines[i]);

                    if (!match->Success)
                    {
                        delete regex;
                        continue;
                    }

                    getEnvir()->CustomCommands.push_back(match->Groups[1]->Value);

                    delete regex;
                }
            }
            else if (Type == NPCScriptType::AutoMonster)
            {
                MonsterInfo *MobInfo;
                if (StringHelper::toUpper(lines[i]).find("SPAWN") != std::string::npos)
                {
                    Regex *regex = new Regex(R"(\((.*?)\))");
                    Match *match = regex->Match(lines[i]);

                    if (!match->Success)
                    {
                        delete regex;
                        continue;
                    }
                    MobInfo = getEnvir()->GetMonsterInfo(StringHelper::fromString<short>(match->Groups[1]->Value));
                    if (MobInfo == nullptr)
                    {
                        delete regex;
                        continue;
                    }
                    MobInfo->HasSpawnScript = true;

                    delete regex;
                }
                if (StringHelper::toUpper(lines[i]).find("DIE") != std::string::npos)
                {
                    Regex *regex = new Regex(R"(\((.*?)\))");
                    Match *match = regex->Match(lines[i]);

                    if (!match->Success)
                    {
                        delete regex;
                        continue;
                    }
                    MobInfo = getEnvir()->GetMonsterInfo(StringHelper::fromString<short>(match->Groups[1]->Value));
                    if (MobInfo == nullptr)
                    {
                        delete regex;
                        continue;
                    }
                    MobInfo->HasDieScript = true;

                    delete regex;
                }
            }
            else if (Type == NPCScriptType::Robot)
            {
                if (StringHelper::toUpper(lines[i]).find("TIME") != std::string::npos)
                {
                    Robot::AddRobot(StringHelper::toUpper(lines[i]));
                }
            }

            NPCPages.AddRange(ParsePages(lines, lines[i]));
        }
    }

    void NPCScript::ParseScript(std::vector<std::string> &lines)
    {
        NPCPages.AddRange(ParsePages(lines));

        ParseGoods(lines);
        ParseTypes(lines);
        ParseQuests(lines);
        ParseCrafting(lines);
        ParseSpeech(lines);
    }

    std::vector<std::string> NPCScript::ParseInsert(std::vector<std::string> &lines)
    {
        std::vector<std::string> newLines;

        for (int i = 0; i < lines.size(); i++)
        {
            if (!StringHelper::startsWith(StringHelper::toUpper(lines[i]), "#INSERT"))
            {
                continue;
            }

            std::vector<std::string> split = StringHelper::split(lines[i], ' ');

            if (split.size() < 2)
            {
                continue;
            }

            std::string path = FileSystem::combine(Settings::EnvirPath, split[1].substr(1, split[1].length() - 2));

            if (!FileSystem::fileExists(path))
            {
                getMessageQueue()->Enqueue(StringHelper::formatSimple("INSERT Script Not Found: {0}", path));
            }
            else
            {
                newLines = File::ReadAllLines(path).ToList();
            }

            lines.insert(lines.end(), newLines.begin(), newLines.end());
        }

        lines.RemoveAll([&] (std::any str)
        {
            return str->ToUpper().StartsWith("#INSERT");
        });

        return lines;
    }

    std::vector<std::string> NPCScript::ParseInclude(std::vector<std::string> &lines)
    {
        for (int i = 0; i < lines.size(); i++)
        {
            if (!StringHelper::startsWith(StringHelper::toUpper(lines[i]), "#INCLUDE"))
            {
                continue;
            }

            std::vector<std::string> split = StringHelper::split(lines[i], ' ');

            std::string path = FileSystem::combine(Settings::EnvirPath, split[1].substr(1, split[1].length() - 2));
            std::string page = ("[" + split[2] + "]").ToUpper();

            bool start = false, finish = false;

            auto parsedLines = std::vector<std::string>();

            if (!FileSystem::fileExists(path))
            {
                getMessageQueue()->Enqueue(StringHelper::formatSimple("INCLUDE Script Not Found: {0}", path));
                return parsedLines;
            }

            std::vector<std::string> extLines = File::ReadAllLines(path);

            for (int j = 0; j < extLines.size(); j++)
            {
                if (!StringHelper::startsWith(StringHelper::toUpper(extLines[j]), page))
                {
                    continue;
                }

                for (int x = j + 1; x < extLines.size(); x++)
                {
                    if (StringHelper::trim(extLines[x]) == ("{"))
                    {
                        start = true;
                        continue;
                    }

                    if (StringHelper::trim(extLines[x]) == ("}"))
                    {
                        finish = true;
                        break;
                    }

                    parsedLines.push_back(extLines[x]);
                }
            }

            if (start && finish)
            {
                lines.InsertRange(i + 1, parsedLines);
                parsedLines.clear();
            }
        }

        lines.RemoveAll([&] (std::any str)
        {
            return str->ToUpper().StartsWith("#INCLUDE");
        });

        return lines;
    }

    std::vector<NPCPage*> NPCScript::ParsePages(std::vector<std::string> &lines, const std::string &key)
    {
        std::vector<NPCPage*> pages;
        std::vector<std::string> buttons;

        NPCPage *page = ParsePage(lines, key);
        pages.push_back(page);

        buttons.insert(buttons.end(), page->Buttons.begin(), page->Buttons.end());

        for (int i = 0; i < buttons.size(); i++)
        {
            std::string section = buttons[i];

            bool match = pages.Any([&] (std::any t)
            {
                return t::Key->ToUpper() == StringHelper::toUpper(section);
            });

            if (match)
            {
                continue;
            }

            page = ParsePage(lines, section);
            buttons.insert(buttons.end(), page->Buttons.begin(), page->Buttons.end());

            pages.push_back(page);
        }

        return pages;
    }

    NPCPage *NPCScript::ParsePage(std::vector<std::string> &scriptLines, const std::string &sectionName)
    {
        bool nextPage = false, nextSection = false;

        std::vector<std::string> lines = scriptLines.Where([&] (std::any x)
        {
            return !x::empty();
        }).ToList();

        NPCPage *Page = new NPCPage(sectionName);

        //Cleans arguments out of search page name
        std::string tempSectionName = Page->ArgumentParse(sectionName);

        //parse all individual pages in a script, defined by sectionName
        for (int i = 0; i < lines.size(); i++)
        {
            std::string line = lines[i];

            if (StringHelper::startsWith(line, ";"))
            {
                continue;
            }

            if (!StringHelper::startsWith(StringHelper::toUpper(lines[i]), StringHelper::toUpper(tempSectionName)))
            {
                continue;
            }

            std::vector<std::string> segmentLines;

            nextPage = false;

            //Found a page, now process that page and split it into segments
            for (int j = i + 1; j < lines.size(); j++)
            {
                std::string nextLine = lines[j];

                if (j < lines.size() - 1)
                {
                    nextLine = lines[j + 1];
                }
                else
                {
                    nextLine = "";
                }

                if (StringHelper::startsWith(nextLine, "[") && StringHelper::endsWith(nextLine, "]"))
                {
                    nextPage = true;
                }

                else if (StringHelper::startsWith(nextLine, "#IF"))
                {
                    nextSection = true;
                }

                if (nextSection || nextPage)
                {
                    segmentLines.push_back(lines[j]);

                    //end of segment, so need to parse it and put into the segment list within the page
                    if (segmentLines.size() > 0)
                    {
                        NPCSegment *segment = ParseSegment(Page, segmentLines);

                        std::vector<std::string> currentButtons;
                        currentButtons.insert(currentButtons.end(), segment->Buttons.begin(), segment->Buttons.end());
                        currentButtons.insert(currentButtons.end(), segment->ElseButtons.begin(), segment->ElseButtons.end());
                        currentButtons.insert(currentButtons.end(), segment->GotoButtons.begin(), segment->GotoButtons.end());

                        Page->Buttons.insert(Page->Buttons.end(), currentButtons.begin(), currentButtons.end());
                        Page->SegmentList.push_back(segment);
                        segmentLines.clear();

                        nextSection = false;
                    }

                    if (nextPage)
                    {
                        break;
                    }

                    continue;
                }

                segmentLines.push_back(lines[j]);
            }

            //bottom of script reached, add all lines found to new segment
            if (segmentLines.size() > 0)
            {
                NPCSegment *segment = ParseSegment(Page, segmentLines);

                std::vector<std::string> currentButtons;
                currentButtons.insert(currentButtons.end(), segment->Buttons.begin(), segment->Buttons.end());
                currentButtons.insert(currentButtons.end(), segment->ElseButtons.begin(), segment->ElseButtons.end());
                currentButtons.insert(currentButtons.end(), segment->GotoButtons.begin(), segment->GotoButtons.end());

                Page->Buttons.insert(Page->Buttons.end(), currentButtons.begin(), currentButtons.end());
                Page->SegmentList.push_back(segment);
                segmentLines.clear();
            }

//C# TO C++ CONVERTER TODO TASK: A 'delete Page' statement was not added since Page was passed to a method or constructor. Handle memory management manually.
            return Page;
        }

//C# TO C++ CONVERTER TODO TASK: A 'delete Page' statement was not added since Page was passed to a method or constructor. Handle memory management manually.
        return Page;
    }

    NPCSegment *NPCScript::ParseSegment(NPCPage *page, std::vector<std::string> &scriptLines)
    {
        std::vector<std::string> checks;

        std::vector<std::string> lines = scriptLines.ToList();
        std::vector<std::string> &currentSay = say, &currentButtons = buttons;

        Regex *regex = new Regex(R"(<.*?/(\@.*?)>)");

        for (int i = 0; i < lines.size(); i++)
        {
            if ((lines[i]).empty())
            {
                continue;
            }

            if (StringHelper::startsWith(lines[i], ";"))
            {
                continue;
            }

            if (StringHelper::startsWith(lines[i], "#"))
            {
//C# TO C++ CONVERTER TODO TASK: There is no C++ equivalent to the .NET String 'Remove' method unless assigning to the string instance:
                std::vector<std::string> action = StringHelper::split(StringHelper::trim(StringHelper::toUpper(lines[i].Remove(0, 1))), ' ');
//C# TO C++ CONVERTER NOTE: The following 'switch' operated on a string and was converted to C++ 'if-else' logic:
//                switch (action[0])
//ORIGINAL LINE: case "IF":
                if (action[0] == "IF")
                {
                        currentSay = checks;
                        currentButtons = std::vector<std::string>();
                        continue;
                }
//ORIGINAL LINE: case "SAY":
                else if (action[0] == "SAY")
                {
                        currentSay = say;
                        currentButtons = buttons;
                        continue;
                }
//ORIGINAL LINE: case "ACT":
                else if (action[0] == "ACT")
                {
                        currentSay = acts;
                        currentButtons = gotoButtons;
                        continue;
                }
//ORIGINAL LINE: case "ELSESAY":
                else if (action[0] == "ELSESAY")
                {
                        currentSay = elseSay;
                        currentButtons = elseButtons;
                        continue;
                }
//ORIGINAL LINE: case "ELSEACT":
                else if (action[0] == "ELSEACT")
                {
                        currentSay = elseActs;
                        currentButtons = gotoButtons;
                        continue;
                }
                else
                {
                        delete regex;
                        throw NotImplementedException();
                }
            }

            if (StringHelper::startsWith(lines[i], "[") && StringHelper::endsWith(lines[i], "]"))
            {
                break;
            }

            if (!currentButtons.empty())
            {
                Match *match = regex->Match(lines[i]);

                while (match->Success)
                {
                    std::string argu = match->Groups[1]->Captures[0]->Value;

                    currentButtons.push_back(StringHelper::formatSimple("[{0}]", argu));
                    match = match->NextMatch();
                }

                //Check if line has a goto command
                auto parts = lines[i].Split(std::vector<char> {' '}, StringSplitOptions::RemoveEmptyEntries);

                if (parts.size()() > 1)
                {
//C# TO C++ CONVERTER NOTE: The following 'switch' operated on a string and was converted to C++ 'if-else' logic:
//                    switch (parts[0].ToUpper())
//ORIGINAL LINE: case "GOTO":
                    if (StringHelper::toUpper(parts[0]) == "GOTO" || StringHelper::toUpper(parts[0]) == "GROUPGOTO")
                    {
                            gotoButtons.push_back(StringHelper::formatSimple("[{0}]", StringHelper::toUpper(parts[1])));
                    }
//ORIGINAL LINE: case "TIMERECALL":
                    else if (StringHelper::toUpper(parts[0]) == "TIMERECALL" || StringHelper::toUpper(parts[0]) == "DELAYGOTO" || StringHelper::toUpper(parts[0]) == "TIMERECALLGROUP")
                    {
                            if (parts.size() > 2)
                            {
                                gotoButtons.push_back(StringHelper::formatSimple("[{0}]", StringHelper::toUpper(parts[2])));
                            }
                    }
                }
            }

            currentSay.push_back(StringHelper::trimEnd(lines[i]));
        }

        NPCSegment *segment = new NPCSegment(page, say, buttons, elseSay, elseButtons, gotoButtons);

        for (int i = 0; i < checks.size(); i++)
        {
            segment->ParseCheck(checks[i]);
        }

        for (int i = 0; i < acts.size(); i++)
        {
            segment->ParseAct(segment->ActList, acts[i]);
        }

        for (int i = 0; i < elseActs.size(); i++)
        {
            segment->ParseAct(segment->ElseActList, elseActs[i]);
        }


        currentButtons = std::vector<std::string>();
        currentButtons.insert(currentButtons.end(), buttons.begin(), buttons.end());
        currentButtons.insert(currentButtons.end(), elseButtons.begin(), elseButtons.end());
        currentButtons.insert(currentButtons.end(), gotoButtons.begin(), gotoButtons.end());

//C# TO C++ CONVERTER TODO TASK: A 'delete segment' statement was not added since segment was used in a 'return' or 'throw' statement.
        delete regex;
        return segment;
    }

    void NPCScript::ParseTypes(std::vector<std::string> &lines)
    {
        for (int i = 0; i < lines.size(); i++)
        {
            if (!StringHelper::startsWith(StringHelper::toUpper(lines[i]), TypeKey))
            {
                continue;
            }

            while (++i < lines.size())
            {
                if ((lines[i]).empty())
                {
                    continue;
                }

                int index;
                if (!int::TryParse(lines[i], index))
                {
                    break;
                }
                Types.push_back(static_cast<ItemType>(index));
            }
        }

        for (int i = 0; i < lines.size(); i++)
        {
            if (!StringHelper::startsWith(StringHelper::toUpper(lines[i]), UsedTypeKey))
            {
                continue;
            }

            while (++i < lines.size())
            {
                if ((lines[i]).empty())
                {
                    continue;
                }

                int index;
                if (!int::TryParse(lines[i], index))
                {
                    break;
                }
                UsedTypes.push_back(static_cast<ItemType>(index));
            }
        }
    }

    void NPCScript::ParseGoods(std::vector<std::string> &lines)
    {
        for (int i = 0; i < lines.size(); i++)
        {
            if (!StringHelper::startsWith(StringHelper::toUpper(lines[i]), TradeKey))
            {
                continue;
            }

            while (++i < lines.size())
            {
                if (StringHelper::startsWith(lines[i], "["))
                {
                    return;
                }
                if ((lines[i]).empty())
                {
                    continue;
                }

                auto data = lines[i].Split(std::vector<char> {' '}, StringSplitOptions::RemoveEmptyEntries);

                ItemInfo *info = getEnvir()->GetItemInfo(data[0]);
                if (info == nullptr)
                {
                    continue;
                }

                UserItem *goods = getEnvir()->CreateShopItem(info, static_cast<unsigned int>(i));

                if (goods == nullptr || std::find(Goods.begin(), Goods.end(), goods) != Goods.end())
                {
                    getMessageQueue()->Enqueue(StringHelper::formatSimple("Could not find Item: {0}, File: {1}", lines[i], FileName));
                    continue;
                }

                unsigned short count = 1;
                if (data.size() == 2)
                {
                    unsigned short::TryParse(data[1], count);
                }

                goods->Count = count;

                Goods.push_back(goods);
            }
        }
    }

    void NPCScript::ParseQuests(std::vector<std::string> &lines)
    {
        for (int i = 0; i < lines.size(); i++)
        {
            if (!StringHelper::startsWith(StringHelper::toUpper(lines[i]), QuestKey))
            {
                continue;
            }

            auto loadedNPC = NPCObject::Get(LoadedObjectID);

            if (loadedNPC == nullptr)
            {
                return;
            }

            while (++i < lines.size())
            {
                if (StringHelper::startsWith(lines[i], "["))
                {
                    return;
                }
                if ((lines[i]).empty())
                {
                    continue;
                }

                int index;
                int::TryParse(lines[i], index);

                if (index == 0)
                {
                    continue;
                }

                QuestInfo *info = getEnvir()->GetQuestInfo(std::abs(index));

                if (info == nullptr)
                {
                    return;
                }

                if (index > 0)
                {
                    info->NpcIndex = LoadedObjectID;
                }
                else
                {
                    info->setFinishNpcIndex(LoadedObjectID);
                }

                if (loadedNPC->Quests.All([&] (std::any x)
                {
                    return x != info;
                }))
                {
                    loadedNPC->Quests.push_back(info);
                }

            }
        }
    }

    void NPCScript::ParseSpeech(std::vector<std::string> &lines)
    {
        auto loadedNPC = NPCObject::Get(LoadedObjectID);

        if (loadedNPC == nullptr)
        {
            return;
        }

        for (int i = 0; i < lines.size(); i++)
        {
            if (!StringHelper::startsWith(StringHelper::toUpper(lines[i]), SpeechKey))
            {
                continue;
            }

            while (++i < lines.size())
            {
                if ((lines[i]).empty())
                {
                    continue;
                }

                auto parts = StringHelper::split(lines[i], ' ');

                int weight;
                if (parts.size() < 2 || !int::TryParse(parts[0], weight))
                {
                    return;
                }

                NPCSpeech *tempVar = new NPCSpeech();
                tempVar->setWeight(weight);
                tempVar->setMessage(lines[i].substr(parts[0].length() + 1));
                loadedNPC->Speech.push_back(tempVar);

//C# TO C++ CONVERTER TODO TASK: A 'delete tempVar' statement was not added since tempVar was passed to a method or constructor. Handle memory management manually.
            }
        }
    }

    void NPCScript::ParseCrafting(std::vector<std::string> &lines)
    {
        for (int i = 0; i < lines.size(); i++)
        {
            if (!StringHelper::startsWith(StringHelper::toUpper(lines[i]), RecipeKey))
            {
                continue;
            }

            while (++i < lines.size())
            {
                if (StringHelper::startsWith(lines[i], "["))
                {
                    return;
                }
                if ((lines[i]).empty())
                {
                    continue;
                }

                auto data = lines[i].Split(std::vector<char> {' '}, StringSplitOptions::RemoveEmptyEntries);

                ItemInfo *info = getEnvir()->GetItemInfo(data[0]);
                if (info == nullptr)
                {
                    continue;
                }

                RecipeInfo *recipe = getEnvir()->RecipeInfoList.SingleOrDefault([&] (std::any x)
                {
                    x::MatchItem(info->Index);
                });

                if (recipe == nullptr)
                {
                    getMessageQueue()->Enqueue(StringHelper::formatSimple("Could not find recipe: {0}, File: {1}", lines[i], FileName));
                    continue;
                }

                if (recipe->Ingredients.empty())
                {
                    getMessageQueue()->Enqueue(StringHelper::formatSimple("Could not find ingredients: {0}, File: {1}", lines[i], FileName));
                    continue;
                }

                CraftGoods.push_back(recipe);
            }
        }
    }

    void NPCScript::Call(MonsterObject *monster, const std::string &key)
    {
        key = StringHelper::toUpper(key);

        for (int i = 0; i < NPCPages.size(); i++)
        {
            NPCPage *page = NPCPages[i];
//C# TO C++ CONVERTER TODO TASK: The following .NET 'String.Equals' reference is not converted:
            if (!std::string::Equals(page->Key, key, StringComparison::CurrentCultureIgnoreCase))
            {
                continue;
            }

            for (auto segment : *page->SegmentList)
            {
                if (page->BreakFromSegments)
                {
                    page->BreakFromSegments = false;
                    break;
                }

                ProcessSegment(monster, page, segment);
            }
        }
    }

    void NPCScript::Call(const std::string &key)
    {
        key = StringHelper::toUpper(key);

        for (int i = 0; i < NPCPages.size(); i++)
        {
            NPCPage *page = NPCPages[i];
//C# TO C++ CONVERTER TODO TASK: The following .NET 'String.Equals' reference is not converted:
            if (!std::string::Equals(page->Key, key, StringComparison::CurrentCultureIgnoreCase))
            {
                continue;
            }

            for (auto segment : *page->SegmentList)
            {
                if (page->BreakFromSegments)
                {
                    page->BreakFromSegments = false;
                    break;
                }

                ProcessSegment(page, segment);
            }
        }
    }

    void NPCScript::Call(PlayerObject *player, unsigned int objectID, const std::string &key)
    {
        key = StringHelper::toUpper(key);

        if (!player->NPCDelayed)
        {
            if (key != MainKey)
            {
                if (player->NPCObjectID != objectID)
                {
                    return;
                }

                bool found = false;

                for (auto segment : *player->NPCPage->SegmentList)
                {
                    bool result;
                    std::unordered_map<NPCSegment*, bool>::const_iterator player.NPCSuccess_iterator = player.NPCSuccess.find(segment);
                    if (player->NPCSuccess_iterator == player->NPCSuccess.end())
                    {
                        result = player->NPCSuccess_iterator->second;
                        break; //no result for segment ?
                    }
                    else
                    {
                        result = player->NPCSuccess_iterator->second;
                    }

                    if ((result ? segment->Buttons : segment->ElseButtons).Any([&] (std::any s)
                    {
                        return s->ToUpper() == key;
                    }))
                    {
                        found = true;
                    }
                }

                if (!found)
                {
                    getMessageQueue()->Enqueue(StringHelper::formatSimple("Player: {0} was prevented access to NPC key: '{1}' ", player->getName(), key));
                    return;
                }
            }
        }
        else
        {
            player->NPCDelayed = false;
        }

        if (StringHelper::startsWith(key, "[@@") && player->NPCInputStr == "")
        {
            //send off packet to request input
            S::NPCRequestInput *tempVar = new S::NPCRequestInput();
            tempVar->NPCID = player->NPCObjectID;
            tempVar->PageName = key;
            player->Enqueue(tempVar);

//C# TO C++ CONVERTER TODO TASK: A 'delete tempVar' statement was not added since tempVar was passed to a method or constructor. Handle memory management manually.
            return;
        }

        for (int i = 0; i < NPCPages.size(); i++)
        {
            NPCPage *page = NPCPages[i];
//C# TO C++ CONVERTER TODO TASK: The following .NET 'String.Equals' reference is not converted:
            if (!std::string::Equals(page->Key, key, StringComparison::CurrentCultureIgnoreCase))
            {
                continue;
            }

            player->NPCSpeech = std::vector<std::string>();
            player->NPCSuccess.clear();

            for (auto segment : *page->SegmentList)
            {
                if (page->BreakFromSegments)
                {
                    page->BreakFromSegments = false;
                    break;
                }

                ProcessSegment(player, page, segment, objectID);
            }

            Response(player, page);
        }


        player->NPCInputStr = "";
    }

    void NPCScript::Response(PlayerObject *player, NPCPage *page)
    {
        S::NPCResponse *tempVar = new S::NPCResponse();
        tempVar->Page = player->NPCSpeech;
        player->Enqueue(tempVar);

        ProcessSpecial(player, page);

//C# TO C++ CONVERTER TODO TASK: A 'delete tempVar' statement was not added since tempVar was passed to a method or constructor. Handle memory management manually.
    }

    void NPCScript::ProcessSegment(PlayerObject *player, NPCPage *page, NPCSegment *segment, unsigned int objectID)
    {
        player->NPCObjectID = objectID;
        player->NPCScriptID = ScriptID;
        player->NPCSuccess.emplace(segment, segment->Check(player));
        player->NPCPage = page;
    }

    void NPCScript::ProcessSegment(MonsterObject *monster, NPCPage *page, NPCSegment *segment)
    {
        segment->Check(monster);
    }

    void NPCScript::ProcessSegment(NPCPage *page, NPCSegment *segment)
    {
        segment->Check();
    }

    void NPCScript::ProcessSpecial(PlayerObject *player, NPCPage *page)
    {
        std::vector<UserItem*> allGoods;

        auto key = StringHelper::toUpper(page->Key);

//C# TO C++ CONVERTER NOTE: The following 'switch' operated on a string and was converted to C++ 'if-else' logic:
//        switch (key)
//ORIGINAL LINE: case BuyKey:
        if (key == BuyKey || key == BuySellKey)
        {
                auto sentGoods = std::vector<UserItem*>(Goods);

                for (int i = 0; i < Goods.size(); i++)
                {
                    player->CheckItem(Goods[i]);
                }

                if (Settings::GoodsOn)
                {
                    auto callingNPC = NPCObject::Get(player->NPCObjectID);

                    if (callingNPC != nullptr)
                    {
                        for (int i = 0; i < callingNPC->UsedGoods.size(); i++)
                        {
                            player->CheckItem(callingNPC->UsedGoods[i]);
                        }
                    }

                    sentGoods.insert(sentGoods.end(), callingNPC->UsedGoods.begin(), callingNPC->UsedGoods.end());
                }

                S::NPCGoods *tempVar = new S::NPCGoods();
                tempVar->List = sentGoods;
                tempVar->Rate = PriceRate(player);
                tempVar->Type = PanelType::Buy;
                tempVar->HideAddedStats = Settings::GoodsHideAddedStats;
                player->Enqueue(tempVar);

                if (key == BuySellKey)
                {
                    S::NPCSell tempVar2();
                    player->Enqueue(&tempVar2);
                }
        }
//ORIGINAL LINE: case SellKey:
        else if (key == SellKey)
        {
                S::NPCSell tempVar3();
                player->Enqueue(&tempVar3);
        }
//ORIGINAL LINE: case RepairKey:
        else if (key == RepairKey)
        {
                S::NPCRepair *tempVar4 = new S::NPCRepair();
                tempVar4->Rate = PriceRate(player);
                player->Enqueue(tempVar4);
        }
//ORIGINAL LINE: case SRepairKey:
        else if (key == SRepairKey)
        {
                S::NPCSRepair *tempVar5 = new S::NPCSRepair();
                tempVar5->Rate = PriceRate(player);
                player->Enqueue(tempVar5);
        }
//ORIGINAL LINE: case CraftKey:
        else if (key == CraftKey)
        {
                for (int i = 0; i < CraftGoods.size(); i++)
                {
                    player->CheckItemInfo(CraftGoods[i]->Item->Info);
                }

                S::NPCGoods *tempVar6 = new S::NPCGoods();
                tempVar6->List = (from x in CraftGoods where x::CanCraft(player) select x::Item)->ToList();
                tempVar6->Rate = PriceRate(player);
                tempVar6->Type = PanelType::Craft;
                player->Enqueue(tempVar6);
        }
//ORIGINAL LINE: case RefineKey:
        else if (key == RefineKey)
        {
                if (player->Info->CurrentRefine != nullptr)
                {
                    player->ReceiveChat("You're already refining an item.", ChatType::System);
                    S::NPCRefine *tempVar7 = new S::NPCRefine();
                    tempVar7->Rate = (Settings::RefineCost);
                    tempVar7->Refining = true;
                    player->Enqueue(tempVar7);
                    break;

//C# TO C++ CONVERTER TODO TASK: A 'delete tempVar7' statement was not added since tempVar7 was passed to a method or constructor. Handle memory management manually.
                }
                else
                {
                    S::NPCRefine *tempVar8 = new S::NPCRefine();
                    tempVar8->Rate = (Settings::RefineCost);
                    tempVar8->Refining = false;
                    player->Enqueue(tempVar8);

//C# TO C++ CONVERTER TODO TASK: A 'delete tempVar8' statement was not added since tempVar8 was passed to a method or constructor. Handle memory management manually.
                }
        }
//ORIGINAL LINE: case RefineCheckKey:
        else if (key == RefineCheckKey)
        {
                S::NPCCheckRefine tempVar9();
                player->Enqueue(&tempVar9);
        }
//ORIGINAL LINE: case RefineCollectKey:
        else if (key == RefineCollectKey)
        {
                player->CollectRefine();
        }
//ORIGINAL LINE: case ReplaceWedRingKey:
        else if (key == ReplaceWedRingKey)
        {
                S::NPCReplaceWedRing *tempVar10 = new S::NPCReplaceWedRing();
                tempVar10->Rate = Settings::ReplaceWedRingCost;
                player->Enqueue(tempVar10);
        }
//ORIGINAL LINE: case StorageKey:
        else if (key == StorageKey)
        {
                player->SendStorage();
                S::NPCStorage tempVar11();
                player->Enqueue(&tempVar11);
        }
//ORIGINAL LINE: case BuyBackKey:
        else if (key == BuyBackKey)
        {
                    if (Settings::GoodsOn)
                    {
                        auto callingNPC = NPCObject::Get(player->NPCObjectID);

                        if (callingNPC != nullptr)
                        {
                            if (callingNPC->BuyBack.find(player->getName()) == callingNPC->BuyBack.end())
                            {
                                callingNPC->BuyBack[player->getName()] = std::vector<UserItem*>();
                            }

                            for (int i = 0; i < callingNPC->BuyBack[player->getName()].size(); i++)
                            {
                                player->CheckItem(callingNPC->BuyBack[player->getName()][i]);
                            }

                            S::NPCGoods *tempVar12 = new S::NPCGoods();
                            tempVar12->List = callingNPC->BuyBack[player->getName()];
                            tempVar12->Rate = PriceRate(player);
                            tempVar12->Type = PanelType::Buy;
                            player->Enqueue(tempVar12);

//C# TO C++ CONVERTER TODO TASK: A 'delete tempVar12' statement was not added since tempVar12 was passed to a method or constructor. Handle memory management manually.
                        }
                    }
        }
//ORIGINAL LINE: case BuyUsedKey:
        else if (key == BuyUsedKey)
        {
                    if (Settings::GoodsOn)
                    {
                        auto callingNPC = NPCObject::Get(player->NPCObjectID);

                        if (callingNPC != nullptr)
                        {
                            for (int i = 0; i < callingNPC->UsedGoods.size(); i++)
                            {
                                player->CheckItem(callingNPC->UsedGoods[i]);
                            }

                            S::NPCGoods *tempVar13 = new S::NPCGoods();
                            tempVar13->List = callingNPC->UsedGoods;
                            tempVar13->Rate = PriceRate(player);
                            tempVar13->Type = PanelType::BuySub;
                            tempVar13->HideAddedStats = Settings::GoodsHideAddedStats;
                            player->Enqueue(tempVar13);

//C# TO C++ CONVERTER TODO TASK: A 'delete tempVar13' statement was not added since tempVar13 was passed to a method or constructor. Handle memory management manually.
                        }
                    }
        }
//ORIGINAL LINE: case ConsignKey:
        else if (key == ConsignKey)
        {
                S::NPCConsign tempVar14();
                player->Enqueue(&tempVar14);
        }
//ORIGINAL LINE: case MarketKey:
        else if (key == MarketKey)
        {
                player->UserMatch = false;
                player->GetMarket("", ItemType::Nothing);
        }
//ORIGINAL LINE: case GuildCreateKey:
        else if (key == GuildCreateKey)
        {
                if (player->Info->Level < Settings::Guild_RequiredLevel)
                {
                    player->ReceiveChat(StringHelper::formatSimple("You have to be at least level {0} to create a guild.", Settings::Guild_RequiredLevel), ChatType::System);
                }
                else if (player->MyGuild == nullptr)
                {
                    player->CanCreateGuild = true;
                    S::GuildNameRequest tempVar15();
                    player->Enqueue(&tempVar15);
                }
                else
                {
                    player->ReceiveChat("You are already part of a guild.", ChatType::System);
                }
        }
//ORIGINAL LINE: case RequestWarKey:
        else if (key == RequestWarKey)
        {
                if (player->MyGuild != nullptr)
                {
                    if (player->MyGuildRank != player->MyGuild->Ranks[0])
                    {
                        player->ReceiveChat("You must be the leader to request a war.", ChatType::System);

//C# TO C++ CONVERTER TODO TASK: A 'delete tempVar10' statement was not added since tempVar10 was passed to a method or constructor. Handle memory management manually.
//C# TO C++ CONVERTER TODO TASK: A 'delete tempVar6' statement was not added since tempVar6 was passed to a method or constructor. Handle memory management manually.
//C# TO C++ CONVERTER TODO TASK: A 'delete tempVar5' statement was not added since tempVar5 was passed to a method or constructor. Handle memory management manually.
//C# TO C++ CONVERTER TODO TASK: A 'delete tempVar4' statement was not added since tempVar4 was passed to a method or constructor. Handle memory management manually.
//C# TO C++ CONVERTER TODO TASK: A 'delete tempVar' statement was not added since tempVar was passed to a method or constructor. Handle memory management manually.
                        return;
                    }
                    S::GuildRequestWar tempVar16();
                    player->Enqueue(&tempVar16);
                }
                else
                {
                    player->ReceiveChat(GameLanguage::NotInGuild, ChatType::System);
                }
        }
//ORIGINAL LINE: case SendParcelKey:
        else if (key == SendParcelKey)
        {
                S::MailSendRequest tempVar17();
                player->Enqueue(&tempVar17);
        }
//ORIGINAL LINE: case CollectParcelKey:
        else if (key == CollectParcelKey)
        {

                signed char result = 0;

                if (player->GetMailAwaitingCollectionAmount() < 1)
                {
                    result = -1;
                }
                else
                {
                    for (auto mail : *player->Info->Mail)
                    {
                        if (mail->getParcel())
                        {
                            mail->Collected = true;
                        }
                    }
                }
                S::ParcelCollected *tempVar18 = new S::ParcelCollected();
                tempVar18->Result = result;
                player->Enqueue(tempVar18);
                player->GetMail();
        }
//ORIGINAL LINE: case AwakeningKey:
        else if (key == AwakeningKey)
        {
                S::NPCAwakening tempVar19();
                player->Enqueue(&tempVar19);
        }
//ORIGINAL LINE: case DisassembleKey:
        else if (key == DisassembleKey)
        {
                S::NPCDisassemble tempVar20();
                player->Enqueue(&tempVar20);
        }
//ORIGINAL LINE: case DowngradeKey:
        else if (key == DowngradeKey)
        {
                S::NPCDowngrade tempVar21();
                player->Enqueue(&tempVar21);
        }
//ORIGINAL LINE: case ResetKey:
        else if (key == ResetKey)
        {
                S::NPCReset tempVar22();
                player->Enqueue(&tempVar22);
        }
//ORIGINAL LINE: case PearlBuyKey:
        else if (key == PearlBuyKey)
        {
                for (int i = 0; i < Goods.size(); i++)
                {
                    player->CheckItem(Goods[i]);
                }

                S::NPCPearlGoods *tempVar23 = new S::NPCPearlGoods();
                tempVar23->List = Goods;
                tempVar23->Rate = PriceRate(player);
                tempVar23->Type = PanelType::Buy;
                player->Enqueue(tempVar23);

//C# TO C++ CONVERTER TODO TASK: A 'delete tempVar23' statement was not added since tempVar23 was passed to a method or constructor. Handle memory management manually.
//C# TO C++ CONVERTER TODO TASK: A 'delete tempVar18' statement was not added since tempVar18 was passed to a method or constructor. Handle memory management manually.
//C# TO C++ CONVERTER TODO TASK: A 'delete tempVar10' statement was not added since tempVar10 was passed to a method or constructor. Handle memory management manually.
//C# TO C++ CONVERTER TODO TASK: A 'delete tempVar6' statement was not added since tempVar6 was passed to a method or constructor. Handle memory management manually.
//C# TO C++ CONVERTER TODO TASK: A 'delete tempVar5' statement was not added since tempVar5 was passed to a method or constructor. Handle memory management manually.
//C# TO C++ CONVERTER TODO TASK: A 'delete tempVar4' statement was not added since tempVar4 was passed to a method or constructor. Handle memory management manually.
//C# TO C++ CONVERTER TODO TASK: A 'delete tempVar' statement was not added since tempVar was passed to a method or constructor. Handle memory management manually.
        }
    }

    void NPCScript::Buy(PlayerObject *player, unsigned long long index, unsigned short count)
    {
        UserItem *goods = nullptr;

        for (int i = 0; i < Goods.size(); i++)
        {
            if (Goods[i]->UniqueID != index)
            {
                continue;
            }
            goods = Goods[i];
            break;
        }

        bool isUsed = false;
        bool isBuyBack = false;

        auto callingNPC = NPCObject::Get(player->NPCObjectID);

        if (callingNPC != nullptr)
        {
            if (goods == nullptr)
            {
                for (int i = 0; i < callingNPC->UsedGoods.size(); i++)
                {
                    if (callingNPC->UsedGoods[i]->UniqueID != index)
                    {
                        continue;
                    }
                    goods = callingNPC->UsedGoods[i];
                    isUsed = true;
                    break;
                }
            }

            if (goods == nullptr)
            {
                if (callingNPC->BuyBack.find(player->getName()) == callingNPC->BuyBack.end())
                {
                    callingNPC->BuyBack[player->getName()] = std::vector<UserItem*>();
                }
                for (int i = 0; i < callingNPC->BuyBack[player->getName()].size(); i++)
                {
                    if (callingNPC->BuyBack[player->getName()][i]->UniqueID != index)
                    {
                        continue;
                    }
                    goods = callingNPC->BuyBack[player->getName()][i];
                    isBuyBack = true;
                    break;
                }
            }
        }

        if (goods == nullptr || count == 0 || count > goods->Info->StackSize)
        {
            return;
        }

        if ((isBuyBack || isUsed) && count > goods->Count)
        {
            count = goods->Count;
        }
        else
        {
            goods->Count = count;
        }

        unsigned int cost = goods->Price();
        cost = static_cast<unsigned int>(cost * PriceRate(player));
        unsigned int baseCost = static_cast<unsigned int>(goods->Price() * PriceRate(player, true));

        if (StringHelper::toUpper(player->NPCPage->Key) == PearlBuyKey) //pearl currency
        {
            if (cost > player->Info->PearlCount)
            {
                return;
            }
        }
        else if (cost > player->Account->Gold)
        {
            return;
        }

        UserItem *item = (isBuyBack || isUsed) ? goods : getEnvir()->CreateFreshItem(goods->Info);
        item->Count = goods->Count;

        if (!player->CanGainItem(item))
        {
            return;
        }

        if (StringHelper::toUpper(player->NPCPage->Key) == PearlBuyKey)
        {
            player->IntelligentCreatureLosePearls(static_cast<int>(cost));
        }
        else
        {
            player->Account->Gold -= cost;
            S::LoseGold *tempVar = new S::LoseGold();
            tempVar->Gold = cost;
            player->Enqueue(tempVar);

            if (callingNPC != nullptr && callingNPC->Conq != nullptr)
            {
                callingNPC->Conq->GoldStorage += (cost - baseCost);
            }

//C# TO C++ CONVERTER TODO TASK: A 'delete tempVar' statement was not added since tempVar was passed to a method or constructor. Handle memory management manually.
        }
        player->GainItem(item);

        if (isUsed)
        {
            callingNPC->UsedGoods.Remove(goods); //If used or buyback will destroy whole stack instead of reducing to remaining quantity

            std::vector<UserItem*> newGoodsList;
            newGoodsList.insert(newGoodsList.end(), Goods.begin(), Goods.end());
            newGoodsList.insert(newGoodsList.end(), callingNPC->UsedGoods.begin(), callingNPC->UsedGoods.end());

            callingNPC->NeedSave = true;

            S::NPCGoods *tempVar2 = new S::NPCGoods();
            tempVar2->List = newGoodsList;
            tempVar2->Rate = PriceRate(player);
            tempVar2->HideAddedStats = Settings::GoodsHideAddedStats;
            tempVar2->Type = StringHelper::toUpper(player->NPCPage->Key) == BuyUsedKey ? PanelType::BuySub : PanelType::Buy;
            player->Enqueue(tempVar2);

//C# TO C++ CONVERTER TODO TASK: A 'delete tempVar2' statement was not added since tempVar2 was passed to a method or constructor. Handle memory management manually.
        }

        if (isBuyBack)
        {
            callingNPC->BuyBack[player->getName()].Remove(goods); //If used or buyback will destroy whole stack instead of reducing to remaining quantity
            S::NPCGoods *tempVar3 = new S::NPCGoods();
            tempVar3->List = callingNPC->BuyBack[player->getName()];
            tempVar3->Rate = PriceRate(player);
            tempVar3->HideAddedStats = false;
            player->Enqueue(tempVar3);

//C# TO C++ CONVERTER TODO TASK: A 'delete tempVar3' statement was not added since tempVar3 was passed to a method or constructor. Handle memory management manually.
        }
    }

    void NPCScript::Sell(PlayerObject *player, UserItem *item)
    {
        /* Handle Item Sale */
    }

    void NPCScript::Craft(PlayerObject *player, unsigned long long index, unsigned short count, std::vector<int> &slots)
    {
        S::CraftItem *p = new S::CraftItem();

        RecipeInfo *recipe = nullptr;

        for (int i = 0; i < CraftGoods.size(); i++)
        {
            if (CraftGoods[i]->Item->UniqueID != index)
            {
                continue;
            }
            recipe = CraftGoods[i];
            break;
        }

        UserItem *goods = recipe->Item;

        if (goods == nullptr || count == 0 || count > goods->Info->StackSize)
        {
            player->Enqueue(p);

//C# TO C++ CONVERTER TODO TASK: A 'delete p' statement was not added since p was passed to a method or constructor. Handle memory management manually.
            return;
        }

        if (player->Account->Gold < recipe->Gold)
        {
            player->Enqueue(p);

//C# TO C++ CONVERTER TODO TASK: A 'delete p' statement was not added since p was passed to a method or constructor. Handle memory management manually.
            return;
        }

        bool hasItems = true;

        std::vector<int> usedSlots;

        //Check Tools
        for (auto tool : *recipe->Tools)
        {
            for (int i = 0; i < slots.size(); i++)
            {
                int slot = slots[i];

                if (std::find(usedSlots.begin(), usedSlots.end(), slot) != usedSlots.end())
                {
                    continue;
                }

                if (slot < 0 || slot > player->Info->Inventory.size())
                {
                    continue;
                }

                UserItem *item = player->Info->Inventory[slot];

                if (item == nullptr || item->Info != tool->Info)
                {
                    continue;
                }

                usedSlots.push_back(slot);

                if (static_cast<unsigned int>(std::floor(item->CurrentDura / 1000)) < count)
                {
                    hasItems = false;
                    break;
                }
            }

            if (!hasItems)
            {
                break;
            }
        }

        //Check Ingredients
        for (auto ingredient : *recipe->Ingredients)
        {
            if (ingredient->Count * count > ingredient->Info->StackSize)
            {
                player->Enqueue(p);

//C# TO C++ CONVERTER TODO TASK: A 'delete p' statement was not added since p was passed to a method or constructor. Handle memory management manually.
                return;
            }

            unsigned short amount = static_cast<unsigned short>(ingredient->Count * count);

            for (int i = 0; i < slots.size(); i++)
            {
                int slot = slots[i];

                if (std::find(usedSlots.begin(), usedSlots.end(), slot) != usedSlots.end())
                {
                    continue;
                }

                if (slot < 0 || slot > player->Info->Inventory.size())
                {
                    continue;
                }

                UserItem *item = player->Info->Inventory[slot];

                if (item == nullptr || item->Info != ingredient->Info)
                {
                    continue;
                }

                usedSlots.push_back(slot);

                if (ingredient->CurrentDura < ingredient->MaxDura && ingredient->CurrentDura > item->CurrentDura)
                {
                    hasItems = false;
                    break;
                }

                if (amount > item->Count)
                {
                    hasItems = false;
                    break;
                }

                amount = 0;
                break;
            }

            if (amount > 0)
            {
                hasItems = false;
                break;
            }
        }

        if (!hasItems || usedSlots.size() != (recipe->Tools.size() + recipe->Ingredients.size()))
        {
            player->Enqueue(p);

//C# TO C++ CONVERTER TODO TASK: A 'delete p' statement was not added since p was passed to a method or constructor. Handle memory management manually.
            return;
        }

        if (count > (goods->Info->StackSize / goods->Count) || count < 1)
        {
            player->Enqueue(p);

//C# TO C++ CONVERTER TODO TASK: A 'delete p' statement was not added since p was passed to a method or constructor. Handle memory management manually.
            return;
        }

        UserItem *craftedItem = getEnvir()->CreateFreshItem(goods->Info);
        craftedItem->Count = static_cast<unsigned short>(goods->Count * count);

        if (!player->CanGainItem(craftedItem))
        {
            player->Enqueue(p);

//C# TO C++ CONVERTER TODO TASK: A 'delete p' statement was not added since p was passed to a method or constructor. Handle memory management manually.
            return;
        }

        std::vector<int> usedSlots2;

        //Use Tool Durability
        for (auto tool : *recipe->Tools)
        {
            for (int i = 0; i < slots.size(); i++)
            {
                int slot = slots[i];

                if (std::find(usedSlots2.begin(), usedSlots2.end(), slot) != usedSlots2.end())
                {
                    continue;
                }

                if (slot < 0 || slot > player->Info->Inventory.size())
                {
                    continue;
                }

                UserItem *item = player->Info->Inventory[slot];

                if (item == nullptr || item->Info != tool->Info)
                {
                    continue;
                }

                usedSlots2.push_back(slot);

                player->DamageItem(item, static_cast<int>(count * 1000), true);

                break;
            }
        }

        //Take Ingredients
        for (auto ingredient : *recipe->Ingredients)
        {
            unsigned short amount = static_cast<unsigned short>(ingredient->Count * count);

            for (int i = 0; i < slots.size(); i++)
            {
                int slot = slots[i];

                if (std::find(usedSlots2.begin(), usedSlots2.end(), slot) != usedSlots2.end())
                {
                    continue;
                }

                if (slot < 0 || slot > player->Info->Inventory.size())
                {
                    continue;
                }

                UserItem *item = player->Info->Inventory[slot];

                if (item == nullptr || item->Info != ingredient->Info)
                {
                    continue;
                }

                usedSlots2.push_back(slot);

                if (item->Count > amount)
                {
                    S::DeleteItem *tempVar = new S::DeleteItem();
                    tempVar->UniqueID = item->UniqueID;
                    tempVar->Count = amount;
                    player->Enqueue(tempVar);
                    player->Info->Inventory[slot]->Count -= amount;

//C# TO C++ CONVERTER TODO TASK: A 'delete tempVar' statement was not added since tempVar was passed to a method or constructor. Handle memory management manually.
                    break;

//C# TO C++ CONVERTER TODO TASK: A 'delete tempVar' statement was not added since tempVar was passed to a method or constructor. Handle memory management manually.
                }
                else
                {
                    S::DeleteItem *tempVar2 = new S::DeleteItem();
                    tempVar2->UniqueID = item->UniqueID;
                    tempVar2->Count = item->Count;
                    player->Enqueue(tempVar2);
                    amount -= item->Count;
                    player->Info->Inventory[slot] = nullptr;

//C# TO C++ CONVERTER TODO TASK: A 'delete tempVar2' statement was not added since tempVar2 was passed to a method or constructor. Handle memory management manually.
                }

                break;
            }
        }

        //Take Gold
        player->Account->Gold -= recipe->Gold;
        S::LoseGold *tempVar3 = new S::LoseGold();
        tempVar3->Gold = recipe->Gold;
        player->Enqueue(tempVar3);

        if (getEnvir()->Random->Next(100) >= recipe->Chance + player->Stats[Stat::CraftRatePercent])
        {
            player->ReceiveChat("Crafting attempt failed.", ChatType::System);
        }
        else
        {
            //Give Item
            player->GainItem(craftedItem);
        }

        p->Success = true;
        player->Enqueue(p);

//C# TO C++ CONVERTER TODO TASK: A 'delete tempVar3' statement was not added since tempVar3 was passed to a method or constructor. Handle memory management manually.
//C# TO C++ CONVERTER TODO TASK: A 'delete p' statement was not added since p was passed to a method or constructor. Handle memory management manually.
    }
}
